#pragma once
#ifndef _KH_ENGINE_RESOURCE_H
#define _KH_ENGINE_RESOURCE_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>

#include <CEGUISize.h>
#include <CEGUITexture.h>

#include <hash_map>
#include <set>

namespace kharlia { namespace engine {

class FResource;

struct _PyStrHash: public stdext::hash_compare<str> {
    size_t operator()(str const& s) const {
        return static_cast<size_t>(PyObject_Hash(s.ptr()));
    }
    bool operator()(str const& s1, str const& s2) const {
        return static_cast<size_t>(PyObject_Hash(s1.ptr())) <
               static_cast<size_t>(PyObject_Hash(s2.ptr()));
    }
};

class FResourceManager: public enable_shared_from_this<FResourceManager>,
                        public noncopyable {
public:
    typedef stdext::hash_map<str, weak_ptr<FResource>, _PyStrHash> ResourceMap;

private:
    ResourceMap mResources;

public:
    FResourceManager() {}

    void UnloadAll();
    void UnregisterAll();
    void Register(shared_ptr<FResource> res);
    void Unregister(shared_ptr<FResource> res);
    void Unregister(FResource* res);
    shared_ptr<FResource> GetResource(str name);
    shared_ptr<FResource> CreateResource(object cls, str name);

    ResourceMap const& GetResources() const { return mResources; }
    static object MakeIter(shared_ptr<FResourceManager> self);

    friend void _InitResourceClasses();
    friend void _InitResourceBindings();
};

enum EResourceState {
    RS_UNLOADED,
    RS_LOADING,
    RS_LOADED,
    RS_UNLOADING
};

class FResource: public boost::enable_shared_from_this<FResource>,
                 public noncopyable {
protected:
    weak_ptr<FResourceManager> mManager;
    str mName;
    EResourceState mState;

public:
    FResource(str name);
    virtual ~FResource();

    virtual void Load() = 0;
    virtual void Unload() = 0;
    virtual bool GetManualLoad() const { return false; }
    virtual bool GetCanLoad() const { return true; }
    virtual bool GetIsClientResource() const { return false; }

    shared_ptr<FResourceManager> GetManager() const { return mManager.lock(); }
    str GetName() const { return mName; }
    EResourceState GetState() const { return mState; }
    bool GetIsLoaded() const { return GetState() == RS_LOADED; }
    uint32 GetInnerID() const { return (uint32)this; }
    
    friend void _InitResourceClasses();
    friend void _InitResourceBindings();
    friend class FResourceManager;
    friend struct FResourcePickler;
};

struct FResourcePickler: public pickle_suite {
    static tuple getinitargs(FResource&);
    static tuple getstate(FResource&);
    static void setstate(FResource&, tuple);
    static bool getstate_manages_dict() { return true; }
};

class FTexture: public FResource {
    ui::Texture* mIntTexture; // Internal CEGUI Texture

public:
    FTexture(str name);
    virtual ~FTexture();

    virtual void Load();
    virtual void Unload();
    virtual bool GetCanLoad() const;
    virtual bool GetIsClientResource() const { return true; }
    void DestroyIntTexture();

    CEGUI::Texture* GetIntTexture() const { return mIntTexture; }
    void SetIntTexture(CEGUI::Texture* tex);

    bool GetIsValid() const { return GetIntTexture() != nullptr; }
    uint16 GetWidth() const { return uint16(mIntTexture? mIntTexture->getOriginalDataSize().d_width: 0); }
    uint16 GetHeight() const { return uint16(mIntTexture? mIntTexture->getOriginalDataSize().d_height: 0); }
    FSizeI GetSrcSize() const { return (mIntTexture ? FSizeI(mIntTexture->getOriginalDataSize()) : FSizeI()); }

    friend void _InitResourceClasses();
    friend void _InitResourceBindings();
    friend struct FTexturePickler;
};

inline shared_ptr<FResource> FResourceManager::GetResource(str name) {
    auto it = mResources.find(name);
    return it == mResources.end()? shared_ptr<FResource>(): it->second.lock();
}

struct FTexturePickler: public pickle_suite {
    static tuple getinitargs(FTexture&);
    static tuple getstate(FTexture&);
    static void setstate(FTexture&, tuple);
};

class CSpriteAniDef: public core::CObject, public FResource {
public:
    struct Frame {
        FRectI Rect;
        float Time;
    };

    typedef std::vector<Frame> FrameList;
    typedef std::vector<FrameList> FrameList2D;

    CSpriteAniDef(PyObject* self, str name);
    ~CSpriteAniDef();

    virtual void Load();
    virtual void Unload();
    virtual bool CanLoad() const;

    FrameList2D const& GetFrameLists() const { return mFrameLists; }
    bool GetShouldLoop() const { return mShouldLoop; }

    uint16 GetFrameListCount() const { return mFrameLists.size(); }
    uint16 GetFrameCount(uint listindex) const { return mFrameLists.at(listindex).size(); }
    FrameList const& GetFrameList(uint16 listindex) const {
        return mFrameLists.at(listindex);
    }
    Frame const& GetFrame(uint16 listindex, uint16 frameindex) const {
        return mFrameLists.at(listindex).at(frameindex);
    }
    tuple GetFramePy(uint16 listindex, uint16 frameindex) const;

private:
    object LoadDefFile(object name) {
        return call_method<object>(mSelf, "LoadDefFile", name);
    }

    FrameList2D mFrameLists;
    bool mShouldLoop;
};

}}

ENABLE_BACK_REFERENCE(kharlia::engine::CSpriteAniDef)

#endif